/*
一键Dump Memory ELF至A64Dbg与之对应的缓存目录。
*/

#include <sys/system_properties.h>

#undef fp

// 参见 def adcpp_elf_dump(data) 函数
#define dump_recv_name "adcpp_elf_dump"

#define logprefix "adcpp_elf_dump : "

// 引入dump的实现代码
#include "jni/dump.cpp"
#include "jni/fix.cpp"
#include "jni/main.cpp"

// adcpp的api接口定义在：https://gitee.com/geekneo/A64Dbg/blob/master/adcpp/adcpp.hpp
#define dump2py(...) buf2py(dump_recv_name, __VA_ARGS__)

#define max_file_name 64

// 文件传输协议字段
struct dump_info {
  // 为了简化结构，直接固定名字长度
  char filename[max_file_name];
  // 文件架构，0-arm，1-aarch64
  int filearch;

  // 文件大小和数据
  int filesize;
  char filebuff[0];
};

static const char *elfbase_path = "/data/local/tmp/adcpp-elf-dump.txt";

// 打印执行过的函数，方便调试
struct ExecTracer {
  ExecTracer(const char *fn) : func(fn) {
    printf(logprefix "Enter %s.\n", func);
  }
  ~ExecTracer() { printf(logprefix "Leave %s.\n", func); }

  const char *func;
};
#if 0
#define exec_tracer() ExecTracer exectracer(__FUNCTION__)
#else
#define exec_tracer()
#endif

// 让a64dbg去重定位该函数
extern "C" jint JNI_GetCreatedJavaVMs(JavaVM **, jsize, jsize *);

static JavaVM *currentJVM() {
  JavaVM *jvm;
  jint count;
  jint err = JNI_GetCreatedJavaVMs(&jvm, 1, &count);
  if (err != JNI_OK) {
    return nullptr;
  }
  return jvm;
}

static JNIEnv *currentJENV() {
  JavaVM *jvm = currentJVM();
  if (!jvm) {
    return nullptr;
  }
  JNIEnv *jenv;
  jint err = jvm->AttachCurrentThread(&jenv, nullptr);
  if (err != JNI_OK) {
    return nullptr;
  }
  return jenv;
}

static jobject getGlobalContext(JNIEnv *env) {
  exec_tracer();
  // 获取Activity Thread的实例对象
  jclass activityThread = env->FindClass("android/app/ActivityThread");
  jmethodID currentActivityThread =
      env->GetStaticMethodID(activityThread, "currentActivityThread",
                             "()Landroid/app/ActivityThread;");
  jobject at =
      env->CallStaticObjectMethod(activityThread, currentActivityThread);
  // 获取Application，也就是全局的Context
  jmethodID getApplication = env->GetMethodID(activityThread, "getApplication",
                                              "()Landroid/app/Application;");
  jobject context = env->CallObjectMethod(at, getApplication);
  return context;
}

static std::string getString(JNIEnv *env, jstring jstr) {
  exec_tracer();
  const char *strptr = env->GetStringUTFChars(jstr, 0);
  printf(logprefix "Get %s from jstring %p .\n", strptr, jstr);
  std::string str(strptr);
  env->ReleaseStringUTFChars(jstr, strptr);
  return str;
}

static std::string getTempPath() {
  exec_tracer();
  JNIEnv *env = currentJENV();
  printf(logprefix "Get JNIEnv %p.\n", env);
  if (!env) {
    // 非apk环境
    return "/data/local/tmp/adcpp.elf";
  }

  jclass clsApplication = env->FindClass("android/app/Application");
  jclass clsFile = env->FindClass("java/io/File");
  jobject app = getGlobalContext(env);
  jmethodID getCacheDir =
      env->GetMethodID(clsApplication, "getCacheDir", "()Ljava/io/File;");
  jobject cdir = env->CallObjectMethod(app, getCacheDir);
  jmethodID getPath =
      env->GetMethodID(clsFile, "getPath", "()Ljava/lang/String;");
  jstring jstr = (jstring)env->CallObjectMethod(cdir, getPath);
  auto cachedir = getString(env, jstr);
  return cachedir + "/adcpp.elf";
}

static uint64_t parseELFBase() {
  exec_tracer();
  char elfbase[32];
  memset(elfbase, 0, sizeof(elfbase));
  FILE *fp = fopen(elfbase_path, "r");
  if (!fp) {
    printf(logprefix "Failed to read %s : %s.", elfbase_path, strerror(errno));
    return 0;
  }
  fread(elfbase, 1, sizeof(elfbase), fp);
  fclose(fp);
  char *end;
  auto result = strtoul(&elfbase[0], &end, 16);
  *end = 0;
  printf(logprefix "Get elf base %s from %s.\n", elfbase, elfbase_path);
  return result;
}

static uint64_t parseELFEnd(uint64_t base) {
  exec_tracer();
  /*
  Android Hardware:
  7094b82000-7094bbd000 r--p 00000000 103:0b 491 libc.so
  7094bbd000-7094c3a000 r-xp 0003b000 103:0b 491 libc.so
  7094c3a000-7094c3e000 r--p 000b8000 103:0b 491 libc.so
  7094c3e000-7094c41000 rw-p 000bb000 103:0b 491 libc.so
  */
  /*
  YooPhone:
  04c28000-04dbd000 r--p 00000000 00:00 0 
  04dbd000-04dcc000 rw-p 00000000 00:00 0 
  04dcc000-04dd0000 r--p 00000000 00:00 0 
  04dd0000-04ddf000 rw-p 00000000 00:00 0 
  04ddf000-04f30000 ---p 00000000 00:00 0 
  */
  FILE *fp = fopen("/proc/self/maps", "r");
  char buff[1024];
  uint64_t end = 0;
  while (fgets(buff, sizeof(buff), fp)) {
    char *endptr;
    uint64_t segbase = strtoul(buff, &endptr, 16);
    if (segbase == base) {
      printf(logprefix "Found module %s", buff);
      // 一直搜索到段不连续为止
      uint64_t last_segbase = strtoul(endptr + 1, nullptr, 16);
      while (fgets(buff, sizeof(buff), fp)) {
        printf(logprefix "Searching module end %p, %s", (void *)last_segbase,
               buff);
        segbase = strtoul(buff, &endptr, 16);
        if (segbase != last_segbase || strstr(buff, "---p")) {
          end = last_segbase;
          break;
        }
        last_segbase = strtoul(endptr + 1, nullptr, 16);
      }
      break;
    }
  }
  fclose(fp);
  printf(logprefix "Get elf end %p.\n", (void *)end);
  return end;
}

static void send_dump(const char *path) {
  exec_tracer();
  const char *name = strrchr(path, '/') + 1;
  if (strlen(name) >= max_file_name) {
    printf(logprefix
           "%s is too long, please reset the size of dump_info.name.\n",
           name);
    return;
  }
  FILE *file = fopen(path, "rb");
  if (!file) {
    printf(logprefix "Failed to read %s.\n", path);
    return;
  }
  fseek(file, 0, SEEK_END);
  size_t filelength = ftell(file);
  fseek(file, 0, SEEK_SET);
  char *filebuff = new char[filelength];
  fread(filebuff, 1, filelength, file);
  fclose(file);
  printf(logprefix "Readed file %s, size %ld.\n", name, filelength);

  // 传回至adcpp_elf_dump函数进一步处理
  size_t dumpsize = sizeof(dump_info) + filelength;
  dump_info *dumpbuff = (dump_info *)malloc(dumpsize);
  dumpbuff->filesize = (int)filelength;
#if __arm__
  dumpbuff->filearch = 0;
#else
  dumpbuff->filearch = 1;
#endif
  memcpy(dumpbuff->filebuff, filebuff, filelength);
  strcpy(dumpbuff->filename, name);
  printf(logprefix "Sending %s, %s, %d.\n", dumpbuff->filename,
         dumpbuff->filearch ? "aarch64" : "arm", dumpbuff->filesize);
  dump2py(dumpbuff, dumpsize);
  free(dumpbuff);
  delete[] filebuff;
}

void adc_main(void) {
  printf(logprefix "Running adcpp-elf-dump (Build %s %s)...\n", __DATE__,
         __TIME__);
  auto elfbase = parseELFBase();
  if (elfbase) {
    auto elfend = parseELFEnd(elfbase);
    if (elfend) {
      auto tmppath = getTempPath();
      if (tmppath.length()) {
        char start[32], end[32];
        sprintf(start, "%p", (void *)elfbase);
        sprintf(end, "%p", (void *)elfend);
        // 调用真正dump的入口函数
        char *argv[] = {
            (char *)"adcpp-elf-dump", (char *)"0", (char *)start, (char *)end,
            (char *)tmppath.data(),
        };
        remove(tmppath.data());
        printf(logprefix "Dumping with %s,%s to %s.\n", argv[2], argv[3],
               argv[4]);
        int ret = main(sizeof(argv) / sizeof(argv[0]), &argv[0]);
        printf(logprefix "Dumper return code %d.\n", ret);
        if (access(tmppath.data(), 0) == 0) {
          send_dump(tmppath.data());
        } else {
          printf(logprefix "Failed to dump, no result file.\n");
        }
      } else {
        printf(logprefix "Bad temp path for dump, cannot be empty path.\n");
      }
    } else {
      printf(logprefix "Bad elf end address, cannot be 0.\n");
    }
  } else {
    printf(logprefix "Bad elf base address, cannot be 0.\n");
  }
  printf(logprefix "Finished dumping.\n");
}
